/*
 *  FreeLoader
 *  Copyright (C) 1998-2003  Brian Palmer  <brianp@sginet.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <freeldr.h>

#include <debug.h>
DBG_DEFAULT_CHANNEL(DISK);

#ifdef _M_IX86

BOOLEAN      DriveMapInstalled = FALSE;    // Tells us if we have already installed our drive map int 13h handler code
ULONG        OldInt13HandlerAddress = 0;   // Address of BIOS int 13h handler
ULONG        DriveMapHandlerAddress = 0;   // Linear address of our drive map handler
ULONG        DriveMapHandlerSegOff = 0;    // Segment:offset style address of our drive map handler

#endif // _M_IX86

BOOLEAN DriveMapIsValidDriveString(PCSTR DriveString)
{
    ULONG Index;

    // Now verify that the user has given us appropriate strings
    if ((strlen(DriveString) < 3) ||
        ((DriveString[0] != 'f') && (DriveString[0] != 'F') &&
         (DriveString[0] != 'h') && (DriveString[0] != 'H')) ||
        ((DriveString[1] != 'd') && (DriveString[1] != 'D')))
    {
        return FALSE;
    }

    // Now verify that the user has given us appropriate numbers
    // Make sure that only numeric characters were given
    for (Index = 2; Index < strlen(DriveString); Index++)
    {
        if (DriveString[Index] < '0' || DriveString[Index] > '9')
        {
            return FALSE;
        }
    }

    // Now make sure that they are not outrageous values (i.e. hd90874)
    if ((atoi(&DriveString[2]) < 0) || (atoi(&DriveString[2]) > 0xff))
    {
        return FALSE;
    }

    return TRUE;
}

UCHAR DriveMapGetBiosDriveNumber(PCSTR DeviceName)
{
    UCHAR BiosDriveNumber = 0;

    TRACE("DriveMapGetBiosDriveNumber(%s)\n", DeviceName);

    // If they passed in a number string then just
    // convert it to decimal and return it
    if (DeviceName[0] >= '0' && DeviceName[0] <= '9')
    {
        return (UCHAR)strtoul(DeviceName, NULL, 0);
    }

    // Convert the drive number string into a number
    // 'hd1' = 1
    BiosDriveNumber = atoi(&DeviceName[2]);

    // If it's a hard disk then set the high bit
    if ((DeviceName[0] == 'h' || DeviceName[0] == 'H') &&
        (DeviceName[1] == 'd' || DeviceName[1] == 'D'))
    {
        BiosDriveNumber |= 0x80;
    }

    return BiosDriveNumber;
}

#ifdef _M_IX86

VOID
DriveMapMapDrivesInSection(
    IN ULONG_PTR SectionId)
{
    CHAR           SettingName[80];
    CHAR           SettingValue[80];
    CHAR           Drive1[80];
    CHAR           Drive2[80];
    ULONG          SectionItemCount;
    ULONG          Index;
    ULONG          Index2;
    DRIVE_MAP_LIST DriveMapList;

    if (SectionId == 0)
        return;

    RtlZeroMemory(&DriveMapList, sizeof(DRIVE_MAP_LIST));

    // Get the number of items in this section
    SectionItemCount = IniGetNumSectionItems(SectionId);

    // Loop through each one and check if its a DriveMap= setting
    for (Index=0; Index<SectionItemCount; Index++)
    {
        // Get the next setting from the .ini file section
        if (IniReadSettingByNumber(SectionId, Index, SettingName, sizeof(SettingName), SettingValue, sizeof(SettingValue)))
        {
            if (_stricmp(SettingName, "DriveMap") == 0)
            {
                // Make sure we haven't exceeded the drive map max count
                if (DriveMapList.DriveMapCount >= 4)
                {
                    UiMessageBox("Max DriveMap count exceeded in section [%s]:\n\n%s=%s", ((PINI_SECTION)SectionId)->SectionName, SettingName, SettingValue);
                    continue;
                }

                RtlZeroMemory(Drive1, 80);
                RtlZeroMemory(Drive2, 80);

                strcpy(Drive1, SettingValue);

                // Parse the setting value and separate a string "hd0,hd1"
                // into two strings "hd0" and "hd1"
                for (Index2=0; Index2<strlen(Drive1); Index2++)
                {
                    // Check if this character is the separater character (comma - ',')
                    if (Drive1[Index2] == ',')
                    {
                        Drive1[Index2] = '\0';
                        strcpy(Drive2, &Drive1[Index2+1]);
                        break;
                    }
                }

                // Make sure we got good values before we add them to the map
                if (!DriveMapIsValidDriveString(Drive1) || !DriveMapIsValidDriveString(Drive2))
                {
                    UiMessageBox("Error in DriveMap setting in section [%s]:\n\n%s=%s", ((PINI_SECTION)SectionId)->SectionName, SettingName, SettingValue);
                    continue;
                }

                // Add them to the map
                DriveMapList.DriveMap[(DriveMapList.DriveMapCount * 2)] = DriveMapGetBiosDriveNumber(Drive1);
                DriveMapList.DriveMap[(DriveMapList.DriveMapCount * 2)+1] = DriveMapGetBiosDriveNumber(Drive2);
                DriveMapList.DriveMapCount++;

                TRACE("Mapping BIOS drive 0x%x to drive 0x%x\n", DriveMapGetBiosDriveNumber(Drive1), DriveMapGetBiosDriveNumber(Drive2));
            }
        }
    }

    if (DriveMapList.DriveMapCount)
    {
        TRACE("Installing Int13 drive map for %d drives.\n", DriveMapList.DriveMapCount);
        DriveMapInstallInt13Handler(&DriveMapList);
    }
    else
    {
        TRACE("Removing any previously installed Int13 drive map.\n");
        DriveMapRemoveInt13Handler();
    }
}

VOID DriveMapInstallInt13Handler(PDRIVE_MAP_LIST DriveMap)
{
    ULONG*  RealModeIVT = (ULONG*)UlongToPtr(0x00000000);
    USHORT* BiosLowMemorySize = (USHORT*)ULongToPtr(0x00000413);

#if defined(SARCH_PC98)
    /* FIXME */
    return;
#endif

    if (!DriveMapInstalled)
    {
        // Get the old INT 13h handler address from the vector table
        OldInt13HandlerAddress = RealModeIVT[0x13];

        // Decrease the size of low memory
        (*BiosLowMemorySize)--;

        // Get linear address for drive map handler
        DriveMapHandlerAddress = (ULONG)(*BiosLowMemorySize) << 10;

        // Convert to segment:offset style address
        DriveMapHandlerSegOff = (DriveMapHandlerAddress << 12) & 0xffff0000;
    }

    // Copy the drive map structure to the proper place
    RtlCopyMemory(&DriveMapInt13HandlerMapList, DriveMap, sizeof(DRIVE_MAP_LIST));

    // Set the address of the BIOS INT 13h handler
    DriveMapOldInt13HandlerAddress = OldInt13HandlerAddress;

    // Copy the code to our reserved area
    RtlCopyMemory(UlongToPtr(DriveMapHandlerAddress),
                  &DriveMapInt13HandlerStart,
                  ((PUCHAR)&DriveMapInt13HandlerEnd - (PUCHAR)&DriveMapInt13HandlerStart));

    // Update the IVT
    RealModeIVT[0x13] = DriveMapHandlerSegOff;

    CacheInvalidateCacheData();
    DriveMapInstalled = TRUE;
}

VOID DriveMapRemoveInt13Handler(VOID)
{
    ULONG*  RealModeIVT = (ULONG*)0x00000000;
    USHORT* BiosLowMemorySize = (USHORT*)0x00000413;

#if defined(SARCH_PC98)
    /* FIXME */
    return;
#endif

    if (DriveMapInstalled)
    {
        // Get the old INT 13h handler address from the vector table
        RealModeIVT[0x13] = OldInt13HandlerAddress;

        // Increase the size of low memory
        (*BiosLowMemorySize)++;

        CacheInvalidateCacheData();
        DriveMapInstalled = FALSE;
    }
}

#endif // _M_IX86
